home *** CD-ROM | disk | FTP | other *** search
- /********************************************************************\
- * text.c -- a basic text field *
- * Copyright (C) 1997 Robin D. Clark *
- * *
- * This program is free software; you can redistribute it and/or *
- * modify it under the terms of the GNU General Public License as *
- * published by the Free Software Foundation; either version 2 of *
- * the License, or (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License*
- * along with this program; if not, write to the Free Software *
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
- * *
- * Author: Rob Clark *
- * Internet: rclark@cs.hmc.edu *
- * Address: 609 8th Street *
- * Huntington Beach, CA 92648-4632 *
- \********************************************************************/
-
- #include "wconfig.h"
-
- #include <X11/Xlib.h>
- #include <X11/Xutil.h>
- #include <X11/keysym.h>
- #include <stdlib.h>
- #include <ctype.h>
-
- #include "WindowMaker.h"
- #include "funcs.h"
- #include "text.h"
- #include "actions.h"
-
- /* X11R5 don't have this */
- #ifndef IsPrivateKeypadKey
- #define IsPrivateKeypadKey(keysym) \
- (((KeySym)(keysym) >= 0x11000000) && ((KeySym)(keysym) <= 0x1100FFFF))
- #endif
-
-
- #if 0
- # define ENTER(X) fprintf(stderr,"Entering: %s()\n", X);
- # define LEAVE(X) fprintf(stderr,"Leaving: %s()\n", X);
- # define DEBUG(X) fprintf(stderr,"debug: %s()\n", X);
- #else
- # define ENTER(X)
- # define LEAVE(X)
- # define DEBUG(X)
- #endif
-
- extern Cursor wCursor[WCUR_LAST];
-
- /********************************************************************\
- * The event handler for the text-field: *
- \********************************************************************/
- static void textEventHandler( WObjDescriptor *desc, XEvent *event );
-
- static void handleExpose( WObjDescriptor *desc, XEvent *event );
-
- static void textInsert( WTextInput *wtext, char *txt );
- #if 0
- static void blink(void *data);
- #endif
-
- /********************************************************************\
- * handleKeyPress *
- * handle cursor keys, regular keys, etc. Inserts characters in *
- * text field, at cursor position, if it is a "Normal" key. If *
- * ksym is the delete key, backspace key, etc., the appropriate *
- * action is performed on the text in the text field. Does not *
- * refresh the text field *
- * *
- * Args: wText - the text field *
- * ksym - the key that was pressed *
- * Return: True, unless the ksym is ignored *
- * Global: modifier - the bitfield that keeps track of the modifier *
- * keys that are down *
- \********************************************************************/
- static int
- handleKeyPress( WTextInput *wtext, XKeyEvent *event )
- {
- KeySym ksym;
- char buffer[32];
- int count;
-
- count = XLookupString(event, buffer, 32, &ksym, NULL);
-
- /* Ignore these keys: */
- if( IsFunctionKey(ksym) || IsKeypadKey(ksym) ||
- IsMiscFunctionKey(ksym) || IsPFKey(ksym) ||
- IsPrivateKeypadKey(ksym) )
- /* If we don't handle it, make sure it isn't a key that
- * the window manager needs to see */
- return False;
-
- /* Take care of the cursor keys.. ignore up and down
- * cursor keys */
- else if( IsCursorKey(ksym) )
- {
- int length = wtext->text.length;
- switch(ksym)
- {
- case XK_Home:
- case XK_Begin:
- wtext->text.endPos = 0;
- break;
- case XK_Left:
- wtext->text.endPos--;
- break;
- case XK_Right:
- wtext->text.endPos++;
- break;
- case XK_End:
- wtext->text.endPos = length;
- break;
- default:
- return False;
- }
- /* make sure that the startPos and endPos have values
- * that make sense (ie the are in [0..length] ) */
- if( wtext->text.endPos < 0 )
- wtext->text.endPos = 0;
- if( wtext->text.endPos > length )
- wtext->text.endPos = length;
- wtext->text.startPos = wtext->text.endPos;
- }
- else
- {
- switch(ksym)
- {
- /* Ignore these keys: */
- case XK_Escape:
- wtext->canceled = True;
- case XK_Return:
- wtext->done = True;
- break;
- case XK_Tab:
- case XK_Num_Lock:
- break;
-
- case XK_Delete:
- /* delete after cursor */
- if( (wtext->text.endPos == wtext->text.startPos) &&
- (wtext->text.endPos < wtext->text.length) )
- wtext->text.endPos++;
- textInsert( wtext, "" );
- break;
- case XK_BackSpace:
- /* delete before cursor */
- if( (wtext->text.endPos == wtext->text.startPos) &&
- (wtext->text.startPos > 0) )
- wtext->text.startPos--;
- textInsert( wtext, "" );
- break;
- default:
- if (count==1 && !iscntrl(buffer[0])) {
- buffer[count] = 0;
- textInsert( wtext, buffer);
- }
- }
- }
- return True;
- }
-
-
-
- /********************************************************************\
- * textXYtoPos *
- * given X coord, return position in array *
- \********************************************************************/
- static int
- textXtoPos( WTextInput *wtext, int x )
- {
- int pos;
- x -= wtext->xOffset;
-
- for( pos=0; wtext->text.txt[pos] != '\0'; pos++ )
- {
- if( x < 0 )
- break;
- else
- x -= WMWidthOfString( wtext->font, &(wtext->text.txt[pos]), 1 );
- }
-
- return pos;
- }
-
- /********************************************************************\
- * wTextCreate *
- * create an instance of a text class *
- * *
- * Args: *
- * Return: *
- * Global: dpy - the display *
- \********************************************************************/
- WTextInput *
- wTextCreate( WCoreWindow *core, int x, int y, int width, int height )
- {
- WTextInput *wtext;
-
- ENTER("wTextCreate");
-
- wtext = wmalloc(sizeof(WTextInput));
- wtext->core = wCoreCreate( core, x, y, width, height );
- wtext->core->descriptor.handle_anything = &textEventHandler;
- wtext->core->descriptor.handle_expose = &handleExpose;
- wtext->core->descriptor.parent_type = WCLASS_TEXT_INPUT;
- wtext->core->descriptor.parent = wtext;
-
- wtext->font = core->screen_ptr->menu_entry_font;
-
- XDefineCursor( dpy, wtext->core->window, wCursor[WCUR_TEXT] );
-
- /* setup the text: */
- wtext->text.txt = (char *)wmalloc(sizeof(char));
- wtext->text.txt[0] = '\0';
- wtext->text.length = 0;
- wtext->text.startPos = 0;
- wtext->text.endPos = 0;
-
- {
- XGCValues gcv;
-
- gcv.foreground = core->screen_ptr->black_pixel;
- gcv.background = core->screen_ptr->white_pixel;
- gcv.line_width = 1;
- gcv.function = GXcopy;
-
- wtext->gc = XCreateGC( dpy, wtext->core->window,
- (GCForeground|GCBackground|
- GCFunction|GCLineWidth),
- &gcv );
-
- /* set up the regular context */
- gcv.foreground = core->screen_ptr->black_pixel;
- gcv.background = core->screen_ptr->white_pixel;
- gcv.line_width = 1;
- gcv.function = GXcopy;
-
- wtext->regGC = XCreateGC( dpy, wtext->core->window,
- (GCForeground|GCBackground|
- GCFunction|GCLineWidth),
- &gcv );
-
- /* set up the inverted context */
- gcv.function = GXcopyInverted;
-
- wtext->invGC = XCreateGC( dpy, wtext->core->window,
- (GCForeground|GCBackground|
- GCFunction|GCLineWidth),
- &gcv );
-
- /* and set the background! */
- XSetWindowBackground( dpy, wtext->core->window, gcv.background );
- }
-
- /* Figure out the y-offset... */
- wtext->yOffset = (height - WMFontHeight(wtext->font))/2;
- wtext->xOffset = wtext->yOffset;
-
- wtext->canceled = False;
- wtext->done = False; /* becomes True when the user *
- * hits "Return" key */
-
- XMapRaised(dpy, wtext->core->window);
-
- LEAVE("wTextCreate");
-
- return wtext;
- }
-
- /********************************************************************\
- * wTextDestroy *
- * *
- * Args: wtext - the text field *
- * Return: *
- * Global: dpy - the display *
- \********************************************************************/
- void
- wTextDestroy( WTextInput *wtext )
- {
- ENTER("wTextDestroy")
-
- #if 0
- if (wtext->magic)
- wDeleteTimerHandler(wtext->magic);
- wtext->magic = NULL;
- #endif
- XFreeGC( dpy, wtext->gc );
- XFreeGC( dpy, wtext->regGC );
- XFreeGC( dpy, wtext->invGC );
- free( wtext->text.txt );
- wCoreDestroy( wtext->core );
- free( wtext );
-
- LEAVE("wTextDestroy");
- }
-
-
- /* The text-field consists of a frame drawn around the outside,
- * and a textbox inside. The space between the frame and the
- * text-box is the xOffset,yOffset. When the text needs refreshing,
- * we only have to redraw the part inside the text-box, and we can
- * leave the frame. If we get an expose event, or for some reason
- * need to redraw the frame, wTextPaint will redraw the frame, and
- * then call wTextRefresh to redraw the text-box */
-
-
- /********************************************************************\
- * textRefresh *
- * Redraw the text field. Call this after messing with the text *
- * field. wTextRefresh re-draws the inside of the text field. If *
- * the frame-area of the text-field needs redrawing, call *
- * wTextPaint() *
- * *
- * Args: wtext - the text field *
- * Return: none *
- * Global: dpy - the display *
- \********************************************************************/
- static void
- textRefresh( WTextInput *wtext )
- {
- int x1,x2,y1,y2;
- char *ptr = wtext->text.txt;
-
- /* x1,y1 is the upper left corner of the text box */
- x1 = wtext->xOffset;
- y1 = wtext->yOffset;
- /* x2,y2 is the lower right corner of the text box */
- x2 = wtext->core->width - wtext->xOffset;
- y2 = wtext->core->height - wtext->yOffset;
-
- /* Fill in the text field. Use the invGC to draw the rectangle,
- * becuase then it will be the background color */
- XFillRectangle( dpy, wtext->core->window, wtext->invGC,
- x1, y1, x2-x1, y2-y1 );
-
- /* Draw the text normally */
- WMDrawString(wtext->core->screen_ptr->wmscreen, wtext->core->window,
- wtext->regGC, wtext->font, x1, y1, ptr, wtext->text.length);
-
- /* Draw the selected text */
- if( wtext->text.startPos != wtext->text.endPos )
- {
- int sp,ep;
- /* we need sp < ep */
- if( wtext->text.startPos > wtext->text.endPos )
- {
- sp = wtext->text.endPos;
- ep = wtext->text.startPos;
- }
- else
- {
- sp = wtext->text.startPos;
- ep = wtext->text.endPos;
- }
-
- /* x1,y1 is now the upper-left of the selected area */
- x1 += WMWidthOfString( wtext->font, ptr, sp );
- /* and x2,y2 is the lower-right of the selected area */
- ptr += sp * sizeof(char);
- x2 = x1 + WMWidthOfString( wtext->font, ptr, (ep - sp) );
- /* Fill in the area where the selected text will go: *
- * use the regGC to draw the rectangle, becuase then it *
- * will be the color of the non-selected text */
- XFillRectangle( dpy, wtext->core->window, wtext->regGC,
- x1, y1, x2-x1, y2-y1 );
-
- /* Draw the selected text... use invGC so it will be the
- * opposite color as the filled rectangle */
- WMDrawString(wtext->core->screen_ptr->wmscreen, wtext->core->window,
- wtext->invGC, wtext->font, x1, y1, ptr, (ep - sp));
- }
-
- /* And draw a quick little line for the cursor position */
- x1 = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos )
- + wtext->xOffset;
- XDrawLine( dpy, wtext->core->window, wtext->regGC, x1, 2, x1,
- wtext->core->height - 3 );
- }
-
-
- /********************************************************************\
- * wTextPaint *
- * *
- * Args: wtext - the text field *
- * Return: *
- * Global: dpy - the display *
- \********************************************************************/
- void
- wTextPaint( WTextInput *wtext )
- {
- ENTER("wTextPaint");
-
- /* refresh */
- textRefresh( wtext );
-
- /* Draw box */
- XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0,
- wtext->core->width-1, wtext->core->height-1);
-
- LEAVE("wTextPaint");
- }
-
-
- /********************************************************************\
- * wTextGetText *
- * return the string in the text field wText. DO NOT FREE THE *
- * RETURNED STRING! *
- * *
- * Args: wtext - the text field *
- * Return: the text in the text field (NULL terminated) *
- * Global: *
- \********************************************************************/
- char *
- wTextGetText( WTextInput *wtext )
- {
- if (!wtext->canceled)
- return wtext->text.txt;
- else
- return NULL;
- }
-
- /********************************************************************\
- * wTextPutText *
- * Put the string txt in the text field wText. The text field *
- * needs to be explicitly refreshed after wTextPutText by calling *
- * wTextRefresh(). *
- * The string txt is copied *
- * *
- * Args: wtext - the text field *
- * txt - the new text string... freed by the text field! *
- * Return: none *
- * Global: *
- \********************************************************************/
- void
- wTextPutText( WTextInput *wtext, char *txt )
- {
- int length = strlen(txt);
-
- /* no memory leaks! free the old txt */
- if( wtext->text.txt != NULL )
- free( wtext->text.txt );
-
- wtext->text.txt = (char *)wmalloc((length+1)*sizeof(char));
- strcpy(wtext->text.txt, txt );
- wtext->text.length = length;
- /* By default No text is selected, and the cursor is at the end */
- wtext->text.startPos = length;
- wtext->text.endPos = length;
- }
-
- /********************************************************************\
- * textInsert *
- * Insert some text at the cursor. (if startPos != endPos, *
- * replace the selected text, otherwise insert) *
- * The string txt is copied. *
- * *
- * Args: wText - the text field *
- * txt - the new text string... freed by the text field! *
- * Return: none *
- * Global: *
- \********************************************************************/
- static void
- textInsert( WTextInput *wtext, char *txt )
- {
- char *newTxt;
- int newLen, txtLen, i,j;
- int sp,ep;
-
- /* we need sp < ep */
- if( wtext->text.startPos > wtext->text.endPos )
- {
- sp = wtext->text.endPos;
- ep = wtext->text.startPos;
- }
- else
- {
- sp = wtext->text.startPos;
- ep = wtext->text.endPos;
- }
-
- txtLen = strlen(txt);
- newLen = wtext->text.length + txtLen - (ep - sp) + 1;
-
- newTxt = (char *)malloc(newLen*sizeof(char));
-
- /* copy the old text up to sp */
- for( i=0; i<sp; i++ )
- newTxt[i] = wtext->text.txt[i];
-
- /* insert new text */
- for( j=0; j<txtLen; j++,i++ )
- newTxt[i] = txt[j];
-
- /* copy old text after ep */
- for( j=ep; j<wtext->text.length; j++,i++ )
- newTxt[i] = wtext->text.txt[j];
-
- newTxt[i] = '\0';
-
- /* By default No text is selected, and the cursor is at the end
- * of inserted text */
- wtext->text.startPos = sp+txtLen;
- wtext->text.endPos = sp+txtLen;
-
- free(wtext->text.txt);
- wtext->text.txt = newTxt;
- wtext->text.length = newLen-1;
- }
-
- /********************************************************************\
- * wTextSelect *
- * Select some text. If start == end, then the cursor is moved *
- * to that position. If end == -1, then the text from start to *
- * the end of the text entered in the text field is selected. *
- * The text field is not automatically re-drawn! You must call *
- * wTextRefresh to re-draw the text field. *
- * *
- * Args: wtext - the text field *
- * start - the beginning of the selected text *
- * end - the end of the selected text *
- * Return: none *
- * Global: *
- \********************************************************************/
- void
- wTextSelect( WTextInput *wtext, int start, int end )
- {
- if( end == -1 )
- wtext->text.endPos = wtext->text.length;
- else
- wtext->text.endPos = end;
- wtext->text.startPos = start;
- }
-
- #if 0
- static void
- blink(void *data)
- {
- int x;
- WTextInput *wtext = (WTextInput*)data;
- GC gc;
-
- /* And draw a quick little line for the cursor position */
- if (wtext->blink_on) {
- gc = wtext->regGC;
- wtext->blink_on = 0;
- } else {
- gc = wtext->invGC;
- wtext->blink_on = 1;
- }
- x = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos )
- + wtext->xOffset;
- XDrawLine( dpy, wtext->core->window, gc, x, 2, x, wtext->core->height-3);
-
- if (wtext->blinking)
- wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, data);
- }
- #endif
-
- /********************************************************************\
- * textEventHandler -- handles and dispatches all the events that *
- * the text field class supports *
- * *
- * Args: desc - all we need to know about this object *
- * Return: none *
- * Global: *
- \********************************************************************/
- static void
- textEventHandler( WObjDescriptor *desc, XEvent *event )
- {
- WTextInput *wtext = desc->parent;
- int handled = False; /* has the event been handled */
-
- switch( event->type )
- {
- case MotionNotify:
- /* If the button isn't down, we don't care about the
- * event, but otherwise we want to adjust the selected
- * text so we can wTextRefresh() */
- if( event->xmotion.state & (Button1Mask|Button3Mask|Button2Mask) )
- {
- DEBUG("MotionNotify");
- handled = True;
- wtext->text.endPos = textXtoPos( wtext, event->xmotion.x );
- }
- break;
-
- case ButtonPress:
- DEBUG("ButtonPress");
- handled = True;
- wtext->text.startPos = textXtoPos( wtext, event->xbutton.x );
- wtext->text.endPos = wtext->text.startPos;
- break;
-
- case ButtonRelease:
- DEBUG("ButtonRelease");
- handled = True;
- wtext->text.endPos = textXtoPos( wtext, event->xbutton.x );
- break;
-
- case KeyPress:
- DEBUG("KeyPress");
- handled = handleKeyPress( wtext, &event->xkey );
- break;
-
- case EnterNotify:
- DEBUG("EnterNotify");
- handled = True;
- #if 0
- if (!wtext->magic)
- {
- wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, wtext);
- wtext->blink_on = !wtext->blink_on;
- blink(wtext);
- wtext->blinking = 1;
- }
- #endif
- break;
-
- case LeaveNotify:
- DEBUG("LeaveNotify");
- handled = True;
- #if 0
- wtext->blinking = 0;
- if (wtext->blink_on)
- blink(wtext);
- if (wtext->magic)
- wDeleteTimerHandler(wtext->magic);
- wtext->magic = NULL;
- #endif
- break;
-
- default:
- break;
- }
-
- if( handled )
- textRefresh(wtext);
- else
- WMHandleEvent(event);
-
- return;
- }
-
-
- static void
- handleExpose(WObjDescriptor *desc, XEvent *event)
- {
- wTextPaint(desc->parent);
- }
-
-